/**
 * WTF Engine
 *
 * License... etc.
 **
 * WTF Network Peers
 **
 * Author: Sean Micklethwaite
 **/

#include "net.h"
#include "timer_object.h"

#ifdef __NET__

using namespace WtfEngine;

// Timeout value, in seconds
#define PEER_TIMEOUT 10.0

/**** Peer State Machine ****/

void PeerState::ProcessMessage(Peer * pPeer, Packet<MessagePacket>::tRefa rMessage)
{
	switch((*rMessage)->MessageType) {
		case MessagePacket::MSG_PING:
			pPeer->SendDataPacket(new Packet<MessagePacket>(MessagePacket(MessagePacket::MSG_PONG)));
			break;
	};
};

class CloseState: public PeerState {
	void ProcessPeer(Peer * pPeer)
	{
		PeerManager::GetInstance().UnlockParty(pPeer);
		pPeer->getControlSocket()->SendPacket(
			new Packet<MessagePacket>(MessagePacket(MessagePacket::MSG_ABORT_TRANSACTION))
		);
		pPeer->Destroy();
	};
};

class WaitState: public PeerState {
	WaitState(Peer * pPeer, PeerState::tRefa rNextState, tGameTime tmo)
	{
		new Timer(this, (new StaticFunctor<void, WeakRef<Peer>, PeerState::tRefa>(
			(void (*)(StateMachine<PeerState> *, PeerState *)) &PeerState::SetState))->Bind(rNextState)->Bind(pPeer), tmo);
	};
};


/**
 * The steady state is when two peers are not engaging in active
 * communication, but are members of the same party who need to
 * stay connected.
 **/
class SteadyState: public WaitState {
	SteadyState(Peer * pPeer): WaitState(pPeer, new CloseState(), PEER_TIMEOUT)
	{
	};

	void ProcessMessage(Peer * pPeer, Packet<MessagePacket>::tRefa rMessage)
	{
		WaitState::ProcessMessage(pPeer, rMessage);
		OnCommunication();
	};

	virtual void OnCommunication() {
		// Communication received - reset state to reset timeout
		pPeer->setState(new SteadyState(pPeer));
	};
};

class PartyPeerState: public WaitState {
	PartyPeerState(Peer * pPeer): SteadyState(pPeer)
	{ };

	virtual void OnCommunication() {
		pPeer->setState(new PartyPeerState(pPeer));
	};
};

class PartyMemberState: public SteadyState {
	PartyMemberState(Peer * pPeer): SteadyState(pPeer)
	{ };

	void ProcessMessage(Peer * pPeer, Packet<MessagePacket>::tRefa rMessage)
	{
		switch((*rMessage)->MessageType) {
			//
			// Receipt of new party details from party leader (after merge)
			//
			case MessagePacket::MSG_PARTY_DETAILS:
				PeerManager::GetInstance().setParty(
					new Party(Packet<PartyDetailsMessage>(rMessage)));
				OnCommunication();
				break;

			default:
				SteadyState::ProcessMessage(pPeer, rMessage);
		};
	};

	virtual void OnCommunication() {
		pPeer->setState(new PartyMemberState(pPeer));
	};
};

class PartyLeaderState: public SteadyState {
	PartyLeaderState(Peer * pPeer): SteadyState(pPeer)
	{ };

	virtual void OnCommunication() {
		pPeer->setState(new PartyLeaderState(pPeer));
	};
};


/**
 * Last stage of a party merge - the client awaits confirmation
 * from its peer before setting its party to the new party.
 **/
class ClientFinalizeState: public PeerState {
	Party::tRef mrParty;

	ClientFinalizeState(Party::tRefa rParty): mrParty(rParty)
	{ };

	void ProcessMessage(Peer * pPeer, Packet<MessagePacket>::tRefa rMessage)
	{
		switch (rMessage->MessageType) {
			case MessagePacket::MSG_FINALIZE_TRANSACTION:
				PeerManager::GetInstance().getParty()->SendToAll(
					mrParty->CreatePartyDetails()
				);
				PeerManager::GetInstance().setParty(mrParty);
				PeerManager::GetInstance().UnlockParty(pPeer);

				//
				// Node is now a member of its peer's party, so enter the
				// party member state
				//
				pPeer->setState(new PartyMemberState())
				break;

			default:
				PeerState::ProcessMessage(pPeer, rMessage);
		};
	};
};

/**
 * Upon entry, this state sends party details to the peer, then waits
 * a fixed timeout for a reply (we expect to receive both party details
 * and a transaction start message from the peer)
 **/
class ClientSendPartyDetailsState: public WaitState {
	tGameTime mTimeStamp;
	bool mbHasTransaction;
	bool mbHasPartyDetails;

	Party::tRef mrPeerParty;

public:
	ClientSendPartyDetailsState(Peer * pPeer)
	: WaitState(pPeer, new CloseState(), PEER_TIMEOUT),
	  mTimeStamp(PeerManager::GetInstance().getParty()->getTimeStamp()) 
	{
		pPeer->getControlSocket()->SendPacket(pPeer->getParty()->CreatePartyDetails());
		mbHasTransaction = mbHasPartyDetails = false;
	};

	void ProcessMessage(Peer * pPeer, Packet<MessagePacket>::tRefa rMessage)
	{
		switch (rMessage->MessageType) {
			//
			// Check our party timestamp, and lock the party.
			// XXX: if the party was modified, we cancel the transcation.
			//
			case MessagePacket::MSG_START_TRANSACTION:
				if(mTimeStamp == PeerManager::GetInstance().getParty()->getTimeStamp()
					&& PeerManager::GetInstance().LockParty(pPeer))
				{
					// XXX: Party is now locked, but we may still have to wait for party
					// details from the peer..
					mbHasTransaction = true;
				} else {
					pPeer->SetState(new CloseState());
					return;
				};

				break;
			
			case MessagePacket::MSG_PARTY_DETAILS:
				mrPeerParty = new Party(new Packet<PartyDetailsMessage>(rMessage));
				mbHasPartyDetails = true;
				break;

			default:
				WaitState::ProcessMessage(pPeer, rPacket);
		};

		if(mbHasPartyDetails && mbHasTransaction) {
			Party::tRef rMergedParty = PeerManager::GetInstance().getParty()->Merge(mrPeerParty);
			if(rMergedParty.isNull()) {
				pPeer->SetState(new CloseState());
			} else {
				pPeer->getControlSocket()->SendPacket(new Packet<MessagePacket>(
					MessagePacket(MessagePacket::MSG_CONFIRM_TRANSACTION)));
				pPeer->SetState(new ClientFinalizeState(rMergedParty));
			};
		}
	};
};


class ServerRecvPartyDetailsState: public WaitState {
public:
	ServerRecvPartyDetailsState(Peer * pPeer)
		: WaitState(pPeer, new CloseState(), PEER_TIMEOUT)
	{
	};

	void ProcessMessage(Peer * pPeer, Packet<MessagePacket>::tRefa rMessage)
	{
		switch (rMessage->MessageType) {
};

class ServerRecvPartyDetailsState: public WaitState {
public:
	ServerRecvPartyDetailsState(Peer * pPeer)
		: WaitState(pPeer, new CloseState(), PEER_TIMEOUT)
	{
	};

	void ProcessMessage(Peer * pPeer, Packet<MessagePacket>::tRefa rMessage)
	{
		switch (rMessage->MessageType) {
			case MessageType::MSG_INTRODUCTION:
				Packet<IntroductionMessage>::tRef rIntro = new Packet<IntroductionMessage>(rMessage);
				pPeer->SetDataPort(rIntro->wDataPort);
				pPeer->setState(new ServerRecvPartyDetailsState(pPeer));
				break;

			default:
				WaitState::ProcessMessage(pPeer, rMessage);
		};
	};
};



/**** Peer ****/

Peer::Peer(unsigned id, const WtfEngine::TcpSocket::tRef &rControlSocket, tPeerType type)
: mNodeId(id), mrControlSocket(rControlSocket),
  mPeerType(type)
{
	mrState = (type == PARTY_LEADER
		? new ClientSendIntroductionState()
		: new ServerRecvIntroductionState(this));
};

Peer::~Peer() {
};

void Peer::Process()
{
	mrState->ProcessPeer(this);
};

void Peer::ProcessPacket(Packet<>::tRefa rPacket)
{
	switch ((*rPacket)->PacketType) {
		case PacketData::PACKET_UPDATE:
			ProcessUpdate(reinterpret_cast<const UpdatePacket*>(rPacket->getData()));
			break;
		case PacketData::PACKET_MESSAGE:
			mrState->ProcessMessage(new Packet<MessagePacket>(rPacket));
			break;
		case PacketData::PACKET_PRIVILEGED_COMMAND:
			if(mPeerType == PRIVILEGED) {
				PeerManager::GetInstance().RunPrivilegedCommand(
					reinterpret_cast<const PrivilegedCommand*>(rPacket->getData()));
			};
			break;
		default:
			PeerManager::GetInstance().Log(LOG_WARNING, "Invalid packet type received");
	};
};

void Peer::ProcessUpdate(const UpdatePacket *pUpdate)
{
	
void Peer::SetDataPort(tPort port)
{
	mrDataSocket = new UdpSocket(port);
	mrDataSocket->Connect(mrControlSocket->getIpAddress());
};

void Peer::Destroy()
{
	mrControlSocket->Disconnect();
	mrState = NULL;
};

#endif
